import { _decorator, Asset, Component, game, Label, Node } from 'cc';

const jsb = window.jsb;

const { ccclass, property } = _decorator;

@ccclass('HotUpdate')
export class HotUpdate extends Component {

    @property(Node)
    private checkBtn: Node = null!;

    @property(Node)
    private updateBtn: Node = null!;

    @property(Label)
    private info: Label = null!;

    @property(Label)
    private fileProgress: Label = null!;

    @property(Label)
    private byteProgress: Label = null!;

    @property(Asset)
    private manifestAsset: Asset = null!;

    private storagePath: string = null!;

    private versionCompareHandle: (versionA: string, versionB: string) => number = null!;

    private am: jsb.AssetsManager = null!;

    private updating: boolean = false;
    private canRetry: boolean = false;

    protected onLoad() {

        if (!jsb)
            return;

        this.storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/'));
        console.log("最基本的读写路径=>", this.storagePath);

        jsb.fileUtils.listFiles(this.storagePath).forEach(function (value) {
            console.log("最基本的读写路径目录下文件列表=>", value);
        });

        //设置自己的版本比较处理程序
        this.versionCompareHandle = function (versionA: string, versionB: string) {
            console.log("JS自定义版本比较: version A is " + versionA + ', version B is ' + versionB);
            const vA = versionA.split('.');
            const vB = versionB.split('.');
            for (let i = 0; i < vA.length; ++i) {
                const a = parseInt(vA[i]);
                const b = parseInt(vB[i] || '0');
                if (a === b) {

                } else {
                    return a - b;
                }
            }
            if (vB.length > vA.length) {
                return -1;
            } else {
                return 0;
            }
        };

        //使用空清单url初始化以测试自定义清单
        this.am = new jsb.AssetsManager('', this.storagePath, this.versionCompareHandle);

        //设置验证回调，但是我们还没有md5校验功能，所以只打印一些消息
        //如果验证通过则返回true，否则返回false
        this.am.setVerifyCallback((path: string, asset: jsb.ManifestAsset) => {
            // 当资产被压缩时，我们不需要检查它的md5，因为zip文件已经被删除了。
            const compressed = asset.compressed;
            // 检索正确的md5值。
            const expectedMD5 = asset.md5;
            // asset.path是相对路径，path是绝对路径。
            const relativePath = asset.path;
            // 资产文件的大小，但该值可能不存在。
            const size = asset.size;

            //校验MD5 todo:以二进制读取文件,传给js库进行MD5计算 || 引入C++库在底层 || 改造 AssetsManagerEx::onSuccess函数的验证函数
            var md5 = calculateMD5(path);
            if (md5 === asset.md5)
                return true;
            else
                return false;
        });

        this.info.string = '请检查更新。';
        this.fileProgress.string = '';
        this.byteProgress.string = '';
    }

    private OnClickCheck() {

        if (this.updating)
            return;

        this.updating = true;

        if (this.am.getState() === jsb.AssetsManager.State.UNINITED) {
            const url = this.manifestAsset.nativeUrl;//返回该资源对应的目标平台资源的 URL，如果没有将返回一个空字符串。
            const isLoadedManifest = this.am.loadLocalManifest(url);
        }

        if (!this.am.getLocalManifest() || !this.am.getLocalManifest().isLoaded()) {
            this.info.string = '加载本地的Manifest文件失败';
            this.updating = false;
            return;
        }

        this.am.setEventCallback(this.checkCb.bind(this));
        this.am.checkUpdate();
    }

    private checkCb(event: jsb.EventAssetsManager) {
        console.log('检查Code:', event.getEventCode());

        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                this.info.string = '没有本地Manifest文件,跳过热更新';
                break;

            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                this.info.string = '下载Manifest文件出错||解析Manifest文件出错,跳过热更新';
                break;

            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                this.info.string = '已经是最新的远程版本';
                break;

            case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                this.info.string = `新的版本已经找到,请更新.(${Math.ceil(this.am.getTotalBytes() / 1024)}kb)`;
                this.fileProgress.string = '';
                this.byteProgress.string = '';
                break;

        }

        this.am.setEventCallback(null!);
        this.updating = false;
    }

    private OnClickUpdate() {
        if (this.am && !this.updating) {

            this.updating = true;

            this.am.setEventCallback(this.updateCb.bind(this));

            if (this.am.getState() === jsb.AssetsManager.State.UNINITED) {
                const url = this.manifestAsset.nativeUrl;//返回该资源对应的目标平台资源的 URL，如果没有将返回一个空字符串。
                this.am.loadLocalManifest(url);
            }

            this.am.update();
            this.updateBtn.active = false;
        }
    }

    private updateCb(event: jsb.EventAssetsManager) {
        console.log('更新Code:', event.getEventCode());

        let needRestart = false;
        let failed = false;
        switch (event.getEventCode()) {

            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                this.info.string = '找不到本地清单文件，跳过热更新。';
                failed = true;
                break;

            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                this.byteProgress.string = event.getDownloadedBytes() + ' / ' + event.getTotalBytes();
                this.fileProgress.string = event.getDownloadedFiles() + ' / ' + event.getTotalFiles();
                const msg = event.getMessage();
                if (msg)
                    this.info.string = `更新文件:${msg}`;
                break;

            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                this.info.string = '下载manifest文件失败';
                break;

            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                this.info.string = '已经是最新的远程版本。';
                failed = true;
                break;

            case jsb.EventAssetsManager.UPDATE_FINISHED:
                this.info.string = `更新结束. ${event.getMessage()}`;
                needRestart = true;
                break;

            case jsb.EventAssetsManager.UPDATE_FAILED:
                this.info.string = `更新失败. ${event.getMessage()}`;
                // this.retryBtn.active = true;
                this.updating = false;
                this.canRetry = true;
                // todo: 重走热更新流程去下载那些上次热更新失败的资源 assetsManager.downloadFailedAssets();
                break;

            case jsb.EventAssetsManager.ERROR_UPDATING:
                this.info.string = `资源更新错误: ${event.getAssetId()},${event.getMessage()}`;
                break;

            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                this.info.string = event.getMessage();//解压错误
                break;

            default:
                console.log("更新的时候还有没处理的情况:", JSON.stringify(event));
                break;
        }

        if (failed) {
            this.am.setEventCallback(null!);
            this.updating = false;
        }

        if (needRestart) {
            this.am.setEventCallback(null!);
            //添加 manifest 的搜索路径
            const searchPaths = jsb.fileUtils.getSearchPaths();
            const newPaths = this.am.getLocalManifest().getSearchPaths();
            console.log('新的manifest的搜索路径:', JSON.stringify(newPaths));
            Array.prototype.unshift.apply(searchPaths, newPaths);
            //该值将在游戏启动时被检索并追加到默认搜索路径中，
            // !！！在main.js中重新添加搜索路径是非常重要的，否则新的脚本将不会生效。
            console.log('存储下最新的manifest的路径:', JSON.stringify(searchPaths));
            localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));

            jsb.fileUtils.setSearchPaths(searchPaths);

            this.scheduleOnce(() => {
                console.log("重启游戏~~~~~~~~~~~~~");
                game.restart();
                console.log("重启游戏End~~~~~~~~~~~~~");
            }, 1);
        }

    }
}